Nesta análise, vamos avaliar a performance de tempo de resposta do AQE Python, assim como do ranking de documentos retornados pelo elasticsearch através da consulta expandida pelo AQE. Vamos utilizar as queries definidas na base REGIS para avaliação do AQE Python.
Para esta análise é necessário ter acesso ao AQE Python rodando, bem como ao elasticsearch, apontando as variáveis necessárias no .env.
import json
import os
import re
import requests
import time
import xml.etree.ElementTree as ET
import pandas as pd
import plotly.express as px
from dotenv import load_dotenv
from utils.utils import create_metrics, create_ranking_dataset, \
retrieve_from_elasticsearch
requests.packages.urllib3.util.connection.HAS_IPV6 = False
load_dotenv()
True
Agora vamos realizar as consultas das queries do REGIS para o AQE Python, calculando o tempo de resposta. Para cada query, vamos fazer o request 30 vezes, para identificar também a variação do tempo de resposta.
with open("../data/regis_queries.json", "r") as json_file:
regis_queries = json.loads(json_file.read())
certificate_path = "../PetrobrasCARootCorporativa.crt"
number_of_requests = 30
max_expanded_terms = 5
aqe_base_url = os.getenv("AQE_URL")
df_data = list()
for i in range(number_of_requests):
for query in regis_queries:
query_title = query.get("title")
query_id = query.get("query_id")
url_query = f"{aqe_base_url}?query={query_title}&max_expanded_terms={max_expanded_terms}"
start = time.time()
response = requests.get(
url_query,
verify=certificate_path
)
end = time.time()
response_time = (end - start) * 1000
df_data.append(
query | {
"response": response.text,
"response_time_ms": response_time,
"num_original_terms": query_title.count(" ") + 1,
"num_expanded_terms": len(re.findall("\^[0-1].[0-9]{3}", response.text)),
}
)
response_df = pd.DataFrame(df_data)
response_df.head()
| title | query_id | response | response_time_ms | num_original_terms | num_expanded_terms | |
|---|---|---|---|---|---|---|
| 0 | História da geoquímica na Petrobras | Q1 | ((História da geoquímica na Petrobras) OR ((hi... | 149.003267 | 5 | 16 |
| 1 | Lógica fuzzy aplicada à industria do petróleo | Q2 | ((Lógica fuzzy aplicada à industria do petról... | 111.497164 | 8 | 21 |
| 2 | Simulação de reservatórios usando linhas de fluxo | Q3 | ((Simulação de reservatórios usando linhas de ... | 266.425133 | 7 | 27 |
| 3 | Detecção de exsudações de óleo nas bacias de S... | Q4 | ((Detecção de exsudações de óleo nas bacias de... | 216.995001 | 9 | 24 |
| 4 | Permeabilidade em Marlim | Q5 | ((Permeabilidade em Marlim) OR ((permeabilidad... | 148.746729 | 3 | 14 |
Primeiramente vamos analisar o tempo de resposta das queries de uma maneira geral.
fig = px.histogram(
response_df, x="response_time_ms", nbins=15,
title="Tempo de resposta do AQE Python",
labels={
"response_time_ms": "Tempo de resposta (ms)",
}
).update_layout(
yaxis_title_text="Contagem de requisições",
)
fig.show()
Podemos ver que a distribuição dos tempos de resposta tem uma maior frequência entre os 100 e 150 ms, com uma longa cauda a direita, que chega próximo dos 500 ms.
Vejamos agora o tempo de resposta para cada uma das queries.
fig = px.box(
response_df, x="query_id", y="response_time_ms",
title="Tempo de resposta das queries",
labels={
"query_id": "Query ID",
"response_time_ms": "Tempo de resposta (ms)",
}
)
fig.show()
Podemos ver que o tempo de resposta tem uma variação considerável, em alguns casos variando próximo dos 200 ms.
Vejamos agora as medianas de cada query para ver a tendência central dos tempos de resposta de cada query, atenuando as variações.
data_viz = response_df.groupby(
"query_id"
).agg(
{"response_time_ms": "median"}
).reset_index()
fig = px.bar(
data_viz, x="query_id", y="response_time_ms",
title="Tempo mediano de resposta das queries",
labels={
"query_id": "Query ID",
"response_time_ms": "Mediana do tempo de resposta (ms)",
}
).add_hline(
y=response_df.response_time_ms.median()
)
fig.show()
Podemos ver que a mediana do tempo de resposta fica entre 150 e 200 ms e que mediana da Q14, Q26 e Q34 possui os maiores valores, acima dos 400 ms, enquanto a Q6 possui o menor tempo de resposta, próximo dos 45 ms.
Agora vamos realizar as consultas ao elastic search e criar o dataset de validação, o qual possui informações do ground truth da base de dados REGIS.
cfg = {
"elasticsearch": {
"url": os.getenv("ELASTIC_SEARCH_URL"),
"index": os.getenv("ELASTIC_SEARCH_INDEX"),
"username": os.getenv("ELASTIC_SEARCH_USERNAME"),
"password": os.getenv("ELASTIC_SEARCH_PASSWORD"),
"certificate": certificate_path
}
}
queries = response_df.filter(
items=["query_id", "response"]
).drop_duplicates(
).itertuples(
index=False, name=None
)
queries = list(queries)
ranking_result_df = retrieve_from_elasticsearch(queries, cfg, 24)
ranking_result_df.head()
| query_id | document_id | relevance_ranking | |
|---|---|---|---|
| 0 | Q1 | BR-BG.03967 | 24.116869 |
| 1 | Q1 | BR-BG.03964 | 24.055304 |
| 2 | Q1 | BR-TU.19978 | 23.322130 |
| 3 | Q1 | BR-TU.23386 | 23.018253 |
| 4 | Q1 | BR-BG.03925 | 22.968935 |
ground_truth = pd.read_csv("../data/regis_ground_truth.csv")
ground_truth.head()
| query_id | document_id | relevance | |
|---|---|---|---|
| 0 | Q1 | BR-BG.03944 | 1 |
| 1 | Q1 | BR-BG.03925 | 1 |
| 2 | Q1 | BR-TU.23384 | 0 |
| 3 | Q1 | BR-TU.12209 | 0 |
| 4 | Q1 | BR-BG.04089 | 2 |
ranking_dataset = create_ranking_dataset(ranking_result_df, ground_truth)
ranking_dataset.head()
| query_id | document_id | relevance_ranking | relevance_ground_truth | evaluated | |
|---|---|---|---|---|---|
| 0 | Q1 | BR-BG.03967 | 24.116869 | 3.0 | True |
| 1 | Q1 | BR-BG.03964 | 24.055304 | 2.0 | True |
| 2 | Q1 | BR-TU.19978 | 23.322130 | 0.0 | True |
| 3 | Q1 | BR-TU.23386 | 23.018253 | 0.0 | True |
| 4 | Q1 | BR-BG.03925 | 22.968935 | 1.0 | True |
Agora vamos criar as métricas para cada base de dados e fator e visualizar os resultados.
metrics_df = create_metrics(ranking_dataset, groupby_columns=["query_id"])
metrics_df.head()
| query_id | ndcg | ap | eval_prop | |
|---|---|---|---|---|
| 0 | Q1 | 0.770842 | 0.354762 | 0.890909 |
| 1 | Q10 | 0.956367 | 0.809687 | 0.807692 |
| 2 | Q11 | 0.612916 | 0.291667 | 0.903226 |
| 3 | Q12 | 0.829174 | 0.539198 | 0.900000 |
| 4 | Q13 | 0.959463 | 0.839539 | 1.000000 |
Vamos agora avaliar as métricas de ranking utilizando o NDCG (Normalized Discounted Cumulative Gain) como métrica de desempenho.
fig = px.bar(
metrics_df, x="query_id", y="ndcg",
title="NDCG das queries",
labels={
"query_id": "Query ID",
"ndcg": "NDCG (Normalized Discounted Cumulative Gain)",
}
).add_hline(
y=metrics_df.ndcg.mean(),
annotation_text=f"NDCG médio {metrics_df.ndcg.mean():.4f}"
).update_layout(xaxis={"categoryorder":"total descending"})
fig.show()
Podemos ver que O NDCG médio foi de 81,31% e que apenas três das 34 queries (8,8%) obtiveram um NDCG abaixo dos 60%.
Pudemos ver nesta análise que o tempo de resposta das queries para o AQE Python varia entre 0 e 500 ms, sendo o valor mais frequente fica entre 100 e 150 ms. Vimos também que a métricas de ranking atingida com o AQE Python foi de 81,31%, utilizando o NDCG@24.